package def

import (
	"fmt"
	"unsafe"
)


type Instruction struct {
	OpCode  OpCode
	Idx     ShortIndex
	ExtIdx  ExternalCodeEntityIndex
	Obj     LocalAddr
	Src     LocalAddr
}
const InstructionSize = unsafe.Sizeof(Instruction {})
var _ = (func() struct{} {
	if InstructionSize != 8 { panic("incorrect instruction size") }
	return struct{}{}
})()

type ShortIndex uint8
const ShortIndexBytes = 1
const ShortIndexBits = (ShortIndexBytes * 8)
const ShortIndexMax = ((1 << ShortIndexBits) - 1)
type ShortSize = ShortIndex
const ShortSizeMax = ShortIndexMax
func EnsureShortIndex(i uint) ShortIndex {
	if i <= ShortIndexMax {
		return ShortIndex(i)
	} else {
		panic("short index overflow")
	}
}

type ExternalCodeEntityIndex uint16
const ExternalCodeEntityIndexBytes = 2
const ExternalCodeEntityIndexBits = (8 * ExternalCodeEntityIndexBytes)
const ExternalCodeEntityIndexMax = ((1 << ExternalCodeEntityIndexBits) - 1)
func RequireExternalCodeEntityIndex(i uint) ExternalCodeEntityIndex {
	if i <= ExternalCodeEntityIndexMax {
		return ExternalCodeEntityIndex(i)
	} else {
		panic("external code entity index overflow")
	}
}

type BranchKey uint32
const BranchKeyItemsMax = 4
func MakeBranchKeySingleItem(index ShortIndex) BranchKey {
	return BranchKey(index)
}
func MakeBranchKey(indexes ([] ShortIndex)) BranchKey {
	if !(uint(len(indexes)) <= BranchKeyItemsMax) {
		panic("something went wrong")
	}
	var vec BranchKey = 0
	for _, index := range indexes {
		vec = (vec << ShortIndexBits)
		vec = (vec | BranchKey(index))
	}
	return vec
}
func (vec BranchKey) Decode() ([] ShortIndex) {
	const mask = ((1 << ShortIndexBits) - 1)
	const shift = ShortIndexBits
	return [] ShortIndex {
		ShortIndex(vec & mask),
		ShortIndex((vec >> (shift)) & mask),
		ShortIndex((vec >> (2 * shift)) & mask),
		ShortIndex((vec >> (3 * shift)) & mask),
	}
}

type LocalAddr uint16
type LocalSize = LocalAddr
const LocalAddrBytes = 2
const LocalAddrBits = (8 * LocalAddrBytes)
const LocalAddrMax = ((1 << LocalAddrBits) - 1)
func MustBeLocalAddr(i uint) LocalAddr {
	if i <= LocalAddrMax {
		return LocalAddr(i)
	} else {
		panic("run out of a local address space (function too big)")
	}
}

type ExternalBranchMapping struct {
	HasDefault  bool
	Default     LocalAddr
	AddrMap     map[BranchKey] LocalAddr
}
func (m ExternalBranchMapping) ChooseBranch(key BranchKey) LocalAddr {
	var branch, found = m.AddrMap[key]
	if found {
		return branch
	} else {
		if m.HasDefault {
			return m.Default
		} else {
			panic("matching branch not found")
		}
	}
}

type ExternalUpdateIndexes ([] ShortIndex)

type OpCode uint8
const (
	SIZE    OpCode = iota  // SRC as an unsigned number (local address)
	ARG     // [ ____ IDX ___  ]: Get the nth argument
	STATIC  // [ ____ ___ PTR  ]: Copy a value from a static address
	CTX     // [ ____ ___ PTR  ]: Copy a value from a context address
	NIL     // [ ____ ___ ____ ]: Get a nil value
	FWD     // [ OBJ  ___ ____ ]: Forward to another instruction
	ENUM    // [ OBJ  IDX ____ ]: Create an enum value
	SWITCH  // [ OBJ  EXT ____ ]: Choose a branch by an enum value
	SELECT  // [ OBJ* EXT ____ ]: Choose a branch by several enum values
	CR      // [ OBJ  IDX ____ ]: Make a functional case ref on an enum value
	CRC     // [ OBJ  IDX ____ ]: Make a functional case ref on a case ref
	CRP     // [ OBJ  IDX ____ ]: Make a functional case ref on a proj ref
	TV      // [ OBJ* ___ ____ ]: Create a tuple value
	TVP     // [ OBJ* ___ ____ ]: Parallel variant of TV
	GET     // [ OBJ  IDX ____ ]: Get the value of a field on a tuple value
	SET     // [ OBJ  EXT SRC* ]: Perform a functional update on a tuple value
	PR      // [ OBJ  IDX ____ ]: Make a functional proj ref on a tuple value
	PRP     // [ OBJ  IDX ____ ]: Make a functional proj ref on a proj ref
	CI      // [ OBJ  ___ PTR  ]: Create an interface from a concrete value
	CII     // [ OBJ  ___ PTR  ]: Create an interface from an interface value
	MET     // [ OBJ  IDX ____ ]: Get the nth method of an interface
	LSV     // [ OBJ* ___ ____ ]: Create a variant list
	LSC     // [ OBJ* IDX ____ ]: Create a compact list according to type info
	MPS     // [ OBJ* ___ SRC* ]: Create a map with string key
	MPI     // [ OBJ* ___ SRC* ]: Create a map with integer key
	CL      // [ OBJ* ___ PTR  ]: Create a closure
	CLR     // [ OBJ* ___ PTR  ]: Create a self-referential closure
	INJ     // [ OBJ* ___ SRC  ]: Inject context values to a function value
	CALL    // [ OBJ  ___ SRC  ]: Call a function
)

func MakeSizeInstruction(size LocalSize) Instruction {
	return Instruction {
		OpCode: SIZE,
		Src:    size,
	}
}
func (inst Instruction) ToSize() LocalSize {
	if inst.OpCode != SIZE { panic("invalid operation") }
	return inst.Src
}
func (inst Instruction) String() string {
	switch inst.OpCode {
	case SIZE:
		return fmt.Sprintf("SIZE == %d", inst.ToSize())
	case ARG:
		return fmt.Sprintf("ARG [%d]", inst.Idx)
	case STATIC:
		return fmt.Sprintf("STATIC (%d)", inst.Src)
	case CTX:
		return fmt.Sprintf("CTX (%d)", inst.Src)
	case NIL:
		return "NIL"
	case FWD:
		return fmt.Sprintf("FWD %d", inst.Obj)
	case ENUM:
		return fmt.Sprintf("ENUM %d [%d]", inst.Obj, inst.Idx)
	case SWITCH:
		return fmt.Sprintf("SWITCH %d [<%d>]", inst.Obj, inst.ExtIdx)
	case SELECT:
		return fmt.Sprintf("SELECT %d* [<%d>]", inst.Obj, inst.ExtIdx)
	case CR:
		return fmt.Sprintf("CR %d [%d]", inst.Obj, inst.Idx)
	case CRC:
		return fmt.Sprintf("CRC %d [%d]", inst.Obj, inst.Idx)
	case CRP:
		return fmt.Sprintf("CRP %d [%d]", inst.Obj, inst.Idx)
	case TV:
		return fmt.Sprintf("TV %d*", inst.Obj)
	case TVP:
		return fmt.Sprintf("TVP %d*", inst.Obj)
	case GET:
		return fmt.Sprintf("GET %d [%d]", inst.Obj, inst.Idx)
	case SET:
		return fmt.Sprintf("SET %d [<%d>] %d*", inst.Obj, inst.Idx, inst.Src)
	case PR:
		return fmt.Sprintf("PR %d [%d]", inst.Obj, inst.Idx)
	case PRP:
		return fmt.Sprintf("PRP %d [%d]", inst.Obj, inst.Idx)
	case CI:
		return fmt.Sprintf("CI %d (%d)", inst.Obj, inst.Src)
	case CII:
		return fmt.Sprintf("CII %d (%d)", inst.Obj, inst.Src)
	case MET:
		return fmt.Sprintf("MET %d [%d]", inst.Obj, inst.Idx)
	case LSV:
		return fmt.Sprintf("LSV %d*", inst.Obj)
	case LSC:
		return fmt.Sprintf("LSC %d* [%d]", inst.Obj, inst.Idx)
	case MPS:
		return fmt.Sprintf("MPS %d* %d*", inst.Obj, inst.Src)
	case MPI:
		return fmt.Sprintf("MPI %d* %d*", inst.Obj, inst.Src)
	case CL:
		return fmt.Sprintf("CL %d* (%d)", inst.Obj, inst.Src)
	case CLR:
		return fmt.Sprintf("CLR %d* (%d)", inst.Obj, inst.Src)
	case INJ:
		return fmt.Sprintf("INJ %d* %d", inst.Obj, inst.Src)
	case CALL:
		return fmt.Sprintf("CALL %d %d", inst.Obj, inst.Src)
	default:
		panic("impossible branch")
	}
}


